/**************************************************************
 * client.c - 
 * Copyright (c) 2009  Qin Xiaoxue  <crs.chin@gmail.com>
 *
 * This file is  free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 * 
**************************************************************/


#include <stdio.h>
#include <dlfcn.h>
#include <sys/types.h>
#include <dirent.h>
#include "common.h"
#include "client.h"


struct client_info{
  void *handle;
  struct client_plugin_info *plugin;
  gint count;
};


static GSList *client_table = NULL;


/*
 *load client from @*table library, if @init is true,the @client is
 * inited and stored in @*client ,@client must not be NULL else
 * @client and all the following are ignored
 */
static struct client_plugin_info *load_client_from_table(GSList **table, const gchar *plugin,
															   struct client **client, gboolean init,
															   const gchar *clientname,
															   const char *exec,
															   int argc, char **argv)
{
  GSList *slist = *table;
  struct client_info *client_info;

  if(! plugin || (init && ! client))
    return NULL;

  do{
    if(! slist)
      return NULL;

    client_info = (struct client_info *)slist->data;
    if(! g_strcmp0(client_info->plugin->player_name, plugin))  {
      if(init)  {
		*client = (client_info->plugin->init)(clientname, exec, argc, argv);
		if(! *client)
		  continue;
      }
      client_info->count++;
      return client_info->plugin;
    }
  }while(g_slist_next(slist));

  return NULL;
}


/*
 *load client from system default library, if @init is true,the
 * @client is inited and stored in @*client ,@client must not be NULL
 * else @client and all the following are ignored
 */
static const struct client_plugin_info *load_client_from_lib(GSList **table, const gchar *plugin, 
							     struct client **client, gboolean init,
							     const gchar *clientname,
							     const char *exec,
							     int argc, char **argv)
{
  void *handle;
  DIR *dir;
  struct dirent *item;
  struct client_plugin_info *plugininfo;
  struct client_info *client_info;
  gchar *path;

  if(init && ! client || ! plugin)
    return NULL;

  dir = opendir(CLIENT_BASEDIR);
  if(! dir)  {
#ifndef NDEBUG
    perror("** error open dir" CLIENT_BASEDIR);
#endif
    return NULL;
  }

  while(item = readdir(dir))  {
    path = g_strdup_printf("%s/%s", CLIENT_BASEDIR, item->d_name);
    handle = dlopen(path, RTLD_LAZY);
    g_free(path);
    if(! handle) {
#ifndef NDEBUG
      fprintf(stderr, "** error loading %s:%s\n", item->d_name, dlerror());
#endif
      continue;
    }

    plugininfo = (struct client_plugin_info *)dlsym(handle, CLIENT_EXPORT_SYMBOL_NAME);

    if(! plugininfo)  {
#ifndef NDEBUG
      fprintf(stderr, "** Bad plugin:%s\n", item->d_name);
#endif
      continue;
    }

    if(! g_strcmp0(plugin, plugininfo->player_name))  {
      if(init)  {
	if(! plugininfo->init)  {
	  dlclose(handle);
#ifndef NDEBUG
      fprintf(stderr, "** Bad plugin without init:%s\n", item->d_name);
#endif
	  continue;
	}

	*client = (plugininfo->init)(clientname, exec, argc, argv);
	if(*client)
	  break;
      }else
	break;
    }
    dlclose(handle);
  }

  closedir(dir);

  client_info = g_new0(struct client_info, 1);

  client_info->handle = handle;
  client_info->plugin = plugininfo;
  client_info->count = 1;

  *table = g_slist_append(*table, client_info);

  return plugininfo;
}


static gboolean unload_client_from_table(GSList **table, const gchar *plugin, struct client *client)
{
  GSList *slist = *table;
  struct client_info *client_info;

  if(! *table || ! plugin )//|| ! client)
    return FALSE;

  do{
    client_info = (struct client_info *)slist->data;
    if(! g_strcmp0(client_info->plugin->player_name, plugin))  {
	  if(client)
		(client_info->plugin->exit)(client);
      if(--client_info->count  == 0)  {
	dlclose(client_info->handle);
	*table = g_slist_remove(*table, client_info);
	g_free(client_info);
	return TRUE;
      }
    }
  }while(g_slist_next(slist));

  return FALSE;
}


/*
 *the reference of the @plugin is sure to be decreased and the plugin
 *is remove from @table is it decreased to 0,this is used for bad
 *plugin which cannt init the struct client,use with care
 */
static gboolean unload_client_from_table_force(GSList **table, const gchar *plugin, struct client *client)
{
  GSList *slist = *table;
  struct client_info *client_info;

  if(! *table || ! plugin )
    return FALSE;

  do{
    client_info = (struct client_info *)slist->data;
    if(! g_strcmp0(client_info->plugin->player_name, plugin))  {
      if(client)
	(client_info->plugin->exit)(client);
      if(--client_info->count  == 0)  {
	dlclose(client_info->handle);
	*table = g_slist_remove(*table, client_info);
	g_free(client_info);
      }
    }
  }while(g_slist_next(slist));

  return FALSE;
}



struct client_plugin_info *load_client(const gchar *plugin)
{
  struct client_plugin_info *plugininfo;

  if(! plugin)
    return NULL;


  plugininfo = load_client_from_table(&client_table, plugin, 
				      NULL, FALSE, NULL, NULL, 0, NULL);
  if(plugininfo)
    return plugininfo;

  return  load_client_from_lib(&client_table, plugin,
			       NULL, FALSE, NULL, NULL, 0, NULL);
}



struct client_plugin_info *load_client_file(const gchar *file)
{
  void *handle;
  struct client_plugin_info *plugininfo;
  struct client_info *client_info;

  if(! file)
    return NULL;

  handle = dlopen(file, RTLD_LAZY);
  if(! handle) {
#ifndef NDEBUG
    fprintf(stderr, "** Can't load plugin: %s.\n", dlerror());
#endif
    return NULL;
  }

  plugininfo = (struct client_plugin_info *)dlsym(handle, CLIENT_EXPORT_SYMBOL_NAME);
  if(! plugininfo) {
#ifndef NDEBUG
    perror("** Can't find symbol");
#endif
    return NULL;
  }

  client_info = g_new0(struct client_info, 1);

  client_info->count = 1;
  client_info->handle = handle;
  client_info->plugin = plugininfo;

  client_table = g_slist_append(client_table, client_info);

  return plugininfo;
}


struct client *load_and_init_client(const gchar *plugin, 
				    const gchar *clientname,
				    const char *exec,
				    int argc, char **argv)
{
  struct client *client;

  if(! plugin)
    return NULL;

  if(load_client_from_table(&client_table, plugin, &client, TRUE, 
			    clientname, exec, argc, argv))
    return client;


  if(load_client_from_lib(&client_table, plugin, &client, TRUE,
			  clientname, exec, argc, argv))
    return client;

  return NULL;
}


struct client *load_and_init_client_file(const gchar *file, 
										 const gchar *clientname,
										 const gchar *exec,
										 gchar **plugin_name,
										 int argc, char **argv)
{
  struct client_plugin_info *plugininfo;
  struct client *client;

  plugininfo = load_client_file(file);
  if(! plugininfo)
    return NULL;

  client = (plugininfo->init)(clientname, exec, argc, argv);

  if(client)  {
	if(plugin_name)
	  *plugin_name = g_strdup(plugininfo->player_name);
    return client;
  }

  unload_client_from_table_force(&client_table, plugininfo->player_name, NULL);

  return NULL;
}



gboolean       unload_client(const gchar *plugin, struct client *client)
{
  return unload_client_from_table(&client_table, plugin, client);
}


gchar  **list_client(void)
{
  void *handle;
  DIR *dir;
  struct dirent *item;
  struct client_plugin_info *plugininfo;
  gchar *path;
  gchar **client_list;
  gint i = 0, veclen = 100;

  dir = opendir(CLIENT_BASEDIR);
  if(! dir)  {
#ifndef NDEBUG
    perror("** error open dir" CLIENT_BASEDIR);
#endif
    return NULL;
  }

  client_list = g_new(gchar *, 100);

  while(item = readdir(dir))  {
    path = g_strdup_printf("%s%s", CLIENT_BASEDIR, item->d_name);
    handle = dlopen(path, RTLD_LAZY);
    g_free(path);
    if(! handle) {
#ifndef NDEBUG
      fprintf(stderr, "** error loading %s:%s\n", item->d_name, dlerror());
#endif
      continue;
    }

    plugininfo = (struct client_plugin_info *)dlsym(handle, CLIENT_EXPORT_SYMBOL_NAME);
    if(plugininfo)  {
      if(i == veclen)  {
	veclen += 100;
	client_list = g_realloc(client_list, veclen);
      }
      *(client_list + i++) = g_strdup(plugininfo->player_name);
    }
    dlclose(handle);
  }

  closedir(dir);

  *(client_list + i) = NULL;

  return client_list;
}


/********************* TESTING CODE  ********************************/

#ifdef TEST_CLIENT

int main(int argc, char **argv)
{
  struct client *client;
  const gchar *plugin = "./libclient_xmms2.so.1";

  client = load_and_init_client_file(plugin, NULL, NULL, 0, NULL);
  if(! client)  {
    printf("** error loading plugin.\n");
    return -1;
  }

  client->toggle(client->c_data);

  if(unload_client(client->c_servername, client))
    printf("-- ok unloading plugin.\n");
  else
    printf("** failed to unloading plugin.\n");

  return 0;
}


#endif
